/**
 * @file epd_gfx.c
 */
#include "epd_gfx.h"
#include <string.h>

static inline uint32_t plane_size_bytes(uint16_t w, uint16_t h)
{
    return ((uint32_t)w * (uint32_t)h) / 8U;
}

void epd_gfx_clear_planes(uint8_t *bw, uint8_t *ry, uint32_t size_bytes)
{
    if (bw != NULL) {
        (void)memset(bw, 0, size_bytes); /* 0 => white for BW plane */
    }
    if (ry != NULL) {
        (void)memset(ry, 0, size_bytes); /* 0 => no accent */
    }
}

/* Helper to set 1bpp pixel in bitplane */
static inline void set_px(uint8_t *plane, uint16_t x, uint16_t y, uint16_t width, uint8_t bit)
{
    const uint32_t idx = ((uint32_t)y * (uint32_t)width) + (uint32_t)x;
    const uint32_t byte_index = idx >> 3; /* /8 */
    const uint8_t  bit_offset = (uint8_t)(7U - (idx & 0x7U)); /* MSB-first */
    const uint8_t  mask = (uint8_t)(1U << bit_offset);
    if (bit != 0U) {
        plane[byte_index] = (uint8_t)(plane[byte_index] | mask);
    } else {
        plane[byte_index] = (uint8_t)(plane[byte_index] & ((uint8_t)~mask));
    }
}

static inline uint8_t rgb565_r8(uint16_t px)
{
    uint8_t r5 = (uint8_t)((px >> 11) & 0x1FU);
    return (uint8_t)((r5 << 3) | (r5 >> 2));
}
static inline uint8_t rgb565_g8(uint16_t px)
{
    uint8_t g6 = (uint8_t)((px >> 5) & 0x3FU);
    return (uint8_t)((g6 << 2) | (g6 >> 4));
}
static inline uint8_t rgb565_b8(uint16_t px)
{
    uint8_t b5 = (uint8_t)(px & 0x1FU);
    return (uint8_t)((b5 << 3) | (b5 >> 2));
}

int epd_gfx_convert_rgb565_to_planes(const uint16_t *rgb565,
                                     uint8_t *bw,
                                     uint8_t *ry,
                                     uint16_t width,
                                     uint16_t height)
{
    if ((rgb565 == NULL) || (bw == NULL) || (ry == NULL) || (width == 0U) || (height == 0U)) {
        return -1;
    }
    const uint32_t size = plane_size_bytes(width, height);
    epd_gfx_clear_planes(bw, ry, size);

    /* Thresholds (empirical defaults):
       - Accent detection: R>200 && G<64 && B<64 -> RY=1
       - Black detection:  luma<64 -> BW=1 (if not accent)
       - Else: white (BW=0, RY=0)
       Luma approximation: Y ~= (30*R + 59*G + 11*B)/100 
    */
    for (uint16_t y = 0U; y < height; ++y) {
        for (uint16_t x = 0U; x < width; ++x) {
            const uint32_t idx = ((uint32_t)y * (uint32_t)width) + (uint32_t)x;
            const uint16_t px = rgb565[idx];
            const uint8_t r = rgb565_r8(px);
            const uint8_t g = rgb565_g8(px);
            const uint8_t b = rgb565_b8(px);

            const uint8_t is_accent = (uint8_t)((r > 200U) && (g < 64U) && (b < 64U));
            if (is_accent) {
                set_px(ry, x, y, width, 1U);
                set_px(bw, x, y, width, 0U);
                continue;
            }
            /* luma in [0..255] approx integer */
            const uint16_t luma = (uint16_t)((30U * (uint16_t)r + 59U * (uint16_t)g + 11U * (uint16_t)b) / 100U);
            if (luma < 64U) {
                set_px(bw, x, y, width, 1U);
                set_px(ry, x, y, width, 0U);
            } else {
                /* white */
                set_px(bw, x, y, width, 0U);
                set_px(ry, x, y, width, 0U);
            }
        }
    }

    return 0;
}

int epd_gfx_build_vertical_tricolor_stripes(uint8_t *bw, uint8_t *ry, uint16_t width, uint16_t height)
{
    if ((bw == NULL) || (ry == NULL) || (width == 0U) || (height == 0U)) {
        return -1;
    }
    const uint32_t size = plane_size_bytes(width, height);
    epd_gfx_clear_planes(bw, ry, size);

    /* Heights: exact integer division into 3 bands */
    const uint16_t band_h = (uint16_t)(height / 3U);
    const uint16_t last_band_h = (uint16_t)(height - (2U * band_h)); /* handle remainder safely */

    /* Top band: BLACK */
    for (uint16_t y = 0U; y < band_h; ++y) {
        for (uint16_t x = 0U; x < width; ++x) {
            set_px(bw, x, y, width, 1U); /* black */
            set_px(ry, x, y, width, 0U);
        }
    }

    /* Middle band: WHITE */
    for (uint16_t y = band_h; y < (uint16_t)(2U * band_h); ++y) {
        for (uint16_t x = 0U; x < width; ++x) {
            set_px(bw, x, y, width, 0U); /* white => bw=0 */
            set_px(ry, x, y, width, 0U);
        }
    }

    /* Bottom band: RED (accent plane) */
    const uint16_t start_y = (uint16_t)(2U * band_h);
    const uint16_t end_y = (uint16_t)(start_y + last_band_h);
    for (uint16_t y = start_y; y < end_y; ++y) {
        for (uint16_t x = 0U; x < width; ++x) {
            set_px(bw, x, y, width, 0U); /* red => bw=0 */
            set_px(ry, x, y, width, 1U); /* accent on */
        }
    }

    return 0;
}